#!/usr/bin/python

#Secure Channels Problem Set 2 Task 3
#Jiaqi Weng 115131
#Vasilii Ponteleev 115151
#Compile it with python 3.*.

import random


class SquareOracle: 
  def __init__(self):
     self.coin = random.randint(0,1)
     
  def request(self ,a): 
    if self.coin == 0:
      return (a * a)%(2 ** 32);
    else:      
      return random.getrandbits(32)
    
  def guess(self, expectedCoinValue): 
    return expectedCoinValue == self.coin


class MultiplicationOracle: 
  def __init__(self):
      self.coin = random.randint(0,1)
      
  def request(self, a, b): 
    if self.coin == 0:
      return (a * b)%(2 ** 32);
    else:
      return random.getrandbits(32)
      
  def guess(self,expectedCoinValue): 
    return expectedCoinValue == self.coin


class BaseAdversary:

  def __init__(self):
    self.probabilityCorrelationCoeff = random.randint(1,7)

  #override to impelemnt oracle interactions 
  def calculateOutputs(self, input, oracle):
    self.outputOne = 0
    self.outputTwo = 0

  def tryToGuessWithProbability70(self, oracle, input):
    self.calculateOutputs(input, oracle)
    if self.outputOne == self.outputTwo:
        if oracle.guess(0) and self.probabilityCorrelationCoeff >= 3:
          return 0
        else:
          return 1
    else:
        if oracle.guess(1) and self.probabilityCorrelationCoeff >= 3:
          return 1
        else:
          return 0
  

class SquareAdversaryA(BaseAdversary):

  def calculateOutputs(self, input, oracle):
    if isinstance(oracle,SquareOracle):
      self.outputOne = oracle.request(input)
      self.outputTwo = oracle.request(-input)
    

#Based on A runs the same procedure except uses B's oracles to simulate A's.
class SimulatorB(SquareAdversaryA):
    
  #A - square adversary. We need to constract a new adversary SimulatorB using SquareAdversaryA to attack
  #MultiplicationOracle. Transfrom all A's request a into B's (a,a) and output whatever A outputs.
  #Since there is no much difference just leave it be as a base class.
  def calculateOutputs(self, input, oracle):
    if isinstance(oracle,MultiplicationOracle):
      self.outputOne = oracle.request(input, input)
      self.outputTwo = oracle.request(-input, -input)




trials = 10000
successTrialsA = 0
successTrialsB = 0

for i in range(0,trials):
  oracleA = SquareOracle()
  oracleB = MultiplicationOracle()

  adversaryA = SquareAdversaryA()
  adversaryB = SimulatorB()
  resA = adversaryA.tryToGuessWithProbability70(oracleA, random.getrandbits(32))
  if resA == oracleA.coin:
    successTrialsA = successTrialsA + 1

  resB = adversaryB.tryToGuessWithProbability70(oracleB, random.getrandbits(32))
  if resB == oracleB.coin:
    successTrialsB = successTrialsB + 1  
  
print ("Success probability A", (successTrialsA / trials) * 100)  
print ("Success probability B", (successTrialsB / trials) * 100)  

  


